<!DOCTYPE HTML>
<html>
<head><meta name="generator" content="Hexo 3.9.0">
  <meta charset="utf-8">
  
  <title>Dubbo之服务的注册发现与访问 › 但行好事，莫问前程</title>
  <meta name="author" content="fengbo">
  
  <meta name="description" content="不想当架构师的程序员不是好厨师 ，不认为PHP是世界上最好的编程语言的Python开发者不是一个好的Javaer。">
  
  
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">

  <meta property="og:title" content="Dubbo之服务的注册发现与访问">
  <meta property="og:site_name" content="但行好事，莫问前程">

  
    <meta property="og:image" content="undefined">
  

  <link href="/favicon.png" rel="icon">
  <link rel="alternate" href="/atom.xml" title="但行好事，莫问前程" type="application/atom+xml">
  <link rel="stylesheet" href="/css/style.css" media="screen" type="text/css">
  <!--[if lt IE 9]><script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script><![endif]-->
  

</head>
</html>

<body>
  <header id="header"><div class="meta inner">
  <h1><a href="/">但行好事，莫问前程</a></h1>
  <h2><a href="/">CV工程师，了解面向Google编程的基本原理，有三年使用Google经验，在通往程序员的道路上艰难的爬行着。</a></h2>
  <nav id="main-nav">
    <ul>
      
      <li><a href="/">Home</a></li>
      
      <li><a href="/archives">Archives</a></li>
      
      <li><a href="/atom.xml">RSS</a></li>
      
    </ul>
    <div class="clearfix"></div>
  </nav>
</div>
<div class="clearfix"></div>
</header>
  <div id="content" class="inner">
    <div id="main-col" class="alignleft"><div id="wrapper"><article class="post">
  <div class="post-content">
    <header>
      
  
    <h1 class="title">Dubbo之服务的注册发现与访问</h1>
  

      
        <time datetime="2018-12-28T11:49:14.000Z">2018-12-28</time>
      
    </header>
    <div class="entry">
      
        <h2 id="Dubbo基础"><a href="#Dubbo基础" class="headerlink" title="Dubbo基础"></a>Dubbo基础</h2><h3 id="RPC请求过程"><a href="#RPC请求过程" class="headerlink" title="RPC请求过程"></a>RPC请求过程</h3><p>客户端启动的时候会根据配置的信息（集成Spring的xsd扩展槽，生成一些自定义的XML标签）为需要远程的接口生成代理类，在代理类中会集成网络通讯的逻辑，即将请求的方法和参数序列化到服务器端，服务器执行后将执行结果序列化返回给客户端，所以在客户端看起来像是直接访问服务端的方法。</p>
<h3 id="服务治理和服务发现"><a href="#服务治理和服务发现" class="headerlink" title="服务治理和服务发现"></a>服务治理和服务发现</h3><p>服务治理指管理各个服务的服务。服务治理最核心的问题是服务发现和服务注册。Dubbo引入了一个注册中心的概念，服务的注册和发现主要依赖这个服务中心。具体过程为：</p>
<ol>
<li>服务提供者启动并将服务注册到注册中心注册自己提供的服务。</li>
<li>服务消费者启动，向注册中心订阅自己需要的服务。</li>
<li>注册中心返回服务提供者地址列表给消费者，如果有变更，注册中心将基于长连接推送变更数据给消费者。</li>
<li>服务消费者，从提供者地址列表中，基于软负载均衡算法（默认是基于权重的随机算法），选一台提供者进行调用，如果调用失败，在选另一台调用。</li>
<li>服务消费者和提供者，在内存中累计调用次数和调用时间，定时每分钟发送一次统计数据到监控中心。</li>
</ol>
<h3 id="Dubbo健壮性"><a href="#Dubbo健壮性" class="headerlink" title="Dubbo健壮性"></a>Dubbo健壮性</h3><ul>
<li>监控中心宕机不影响使用，只是丢失部分采样数据。</li>
<li>注册中心对等集群，任意一台宕机后，将自动切换到另一台。</li>
<li><b>注册中心全部宕机后，服务提供者和服务消费者仍能通过本地缓存通信。</b></li>
<li>服务提供者无状态，任意一台宕机后，不影响使用。</li>
<li>服务提供者全部宕机后，服务消费者应用将无法使用，并无限次重连等待服务提供者恢复。</li>
<li>在一定时间内服务提供者没有返回，则认为本次调用失败。失败后会再次调用，如果在配置的调用次数内都失败，则认为此次请求异常，抛出异常。<b>可能引发发送邮件重复或账户注册重复等问题。</b></li>
</ul>
<h3 id="Dubbo保证幂等性"><a href="#Dubbo保证幂等性" class="headerlink" title="Dubbo保证幂等性"></a>Dubbo保证幂等性</h3><p>用户对于同一操作发起的一次请求或者多次请求的结果是一致的，不会因为多次点击而产生了副作用。首先在业务逻辑中就应该注意保证接口的幂等性，而像转账这些无法保证的操作可以考虑使用分布式锁。例如使用redis的setNX（SET if Not eXists）若给定的key已经存在，则setNX不做任何操作。<br>支付操作流程：先查询订单状态如果订单未付款就使用setNX根据订单号设置一个key，当服务调用成功或回滚后才会删除key。然后构建一个本地事务修改订单状态和扣款。转账类似，在确认转账但未输入密码付款前可以生成一条有唯一编号且状态为未付款的交易记录。</p>
<h3 id="Dubbo配套设施"><a href="#Dubbo配套设施" class="headerlink" title="Dubbo配套设施"></a>Dubbo配套设施</h3><p>熔断、限流、降级、隔离，监控、服务测试、api网关、注册中心等。</p>
<ul>
<li>服务注册与发行：Zookeeper和Eureka（不维护）和Nacos。</li>
<li>负载均衡：自带管控台可以进行配置。</li>
<li>服务熔断：Sentinel和Hystrix（不在活跃开发）。</li>
<li>分布式配置：Nacos，即Nacos可以作为sentinel规则配置中心。</li>
</ul>
<h2 id="Dubbo调用"><a href="#Dubbo调用" class="headerlink" title="Dubbo调用"></a>Dubbo调用</h2><h3 id="Dubbo调用原理分析"><a href="#Dubbo调用原理分析" class="headerlink" title="Dubbo调用原理分析"></a>Dubbo调用原理分析</h3><ul>
<li>基于Spring的XML解析扩展机制自定义Dubbo的基于XML的配置格式。</li>
<li>dubbo-config项目的dubbo-config-spring子项目负责解析Dubbo的XML配置文件（主要是解析出Dubbo的注册中心地址、使用的传输协议、服务器端的端口号等）。</li>
<li>解析出配置后服务器端启动，并将自己所能提供的服务注册到注册中心，然后通过dubbo-remoting项目的dubbo-remoting-netty4子项目（dubbo协议）开启服务。</li>
<li>客户端同样通过Spring的XML解析机制解析出配置的各种参数，在通过注册中心或直接配置拿到服务器端的地址，然后通过dubbo-remoting项目的dubbo-remoting-netty4子项目（dubbo协议）连接到服务器端。<a id="more"></a>
</li>
</ul>
<h3 id="Dubbo服务调用过程"><a href="#Dubbo服务调用过程" class="headerlink" title="Dubbo服务调用过程"></a>Dubbo服务调用过程</h3><p>客户端通过一个接口调用方法时，真实调用的是使用Java动态代理、CGLIB、Javassist或者ASM等技术为该接口生成的一个代理对象的方法，该对象的每个方法都会通过使用Netty等网络框架将真正的需要调用的类和方法等参数封装为一个对象并序列化传递到服务器端，服务器端会通过这些参数反射调用真正的业务逻辑实现类并将执行结果序列化并传递回客户端。</p>
<h3 id="服务器端调用链"><a href="#服务器端调用链" class="headerlink" title="服务器端调用链"></a>服务器端调用链</h3><ol>
<li>调用Spring接口解析XML或注解生成一个ServiceBean，该Bean实现了InitializingBean接口，所以在该Bean被Spring容器实例化后会自动调用afterPropertiesSet()方法。</li>
<li>然后调用ServiceBean的父类ServiceConfig的export() –&gt; doExport() –&gt; doExportUrls() –&gt; doExportUrlsFor1Protocol(ProtocolConfig, List<url>)，doExportUrlsFor1Protocol方法会解析出该Bean的传输协议类型。</url></li>
<li>如果是dubbo协议且有注册中先会调用RegistryProtocol的export(Invoker<t>) –&gt; doLocalExport(Invoker<t>)在调用DubboProtocol的export(Invoker<t>)方法。</t></t></t></li>
<li>如果没有注册中心而是使用直连的话会直接调用DubboProtocol的export(Invoker<t>) –&gt; openServer(URL) –&gt; createServer(URL)–&gt; Exchangers的bind(URL, ExchangeHandler)方法 –&gt; HeaderExchanger的bind(URL, ExchangeHandler)方法 –&gt; Transporters的bind(URL, ChannelHandler…)方法 –&gt; NettyTransporter的bind(URL, ChannelHandler)方法。</t></li>
<li>在NettyTransporter的bind()方法中会新建一个NettyServer实例，NettyServer的父类AbstractServer的构造方法会调用NettyServer的doOpen()方法。doOpen方法中实现了Netty构建服务器端的逻辑。</li>
<li>其中DubboProtocol的openServer方法会将创建好的NettyServer实例放入一个Map集合中，集合的key是服务器端的真实地址，所以说服务器端并不是每个service都创建一个网络服务，而是根据每个地址创建一个对应的服务。</li>
<li>在步骤3时，如果有注册中心，在服务导出后需要调用注册服务注册到注册中心。在RegistryProtocol的export(Invoker<t>)方法中调用本类的register(URL registryUrl, URL registedProviderUrl)方法，然后调用AbstractRegistryFactory的getRegistry(URL)方法获取Registry。</t></li>
<li>getRegistry方法会先尝试从缓存中获取Registry，如果缓存中不存在就调用子类的createRegistry(URL)方法创建一个Registry。如果使用Zookeeper作为注册中心，调用的就是ZookeeperRegistryFactory的createRegistry(URL)方法。该方法会构建一个ZookeeperRegistry对象并返回，具体操作注册中心的业务逻辑在该对象中。</li>
</ol>
<h3 id="客户端调用链"><a href="#客户端调用链" class="headerlink" title="客户端调用链"></a>客户端调用链</h3><p>Dubbo引用的时机有两个，第一个是在Spring容器调用ReferenceBean的afterPropertiesSet方法时引用服务，第二个是在ReferenceBean对应的服务被注入到其他类中时引用。这两个引用服务的时机区别在于，第一个是饿汉式的，第二个是懒汉式的。默认情况下，Dubbo使用懒汉式引用服务。<br>Dubbo有三种调用服务的方式，第一种是引用本地(JVM)服务，第二是通过直联方式引用远程服务，第三是通过注册中心引用远程服务。不管是哪种引用方式，最后都会得到一个Invoker实例。如果有多个注册中心，多个服务提供者，这个时候会得到一组Invoker实例，此时需要通过集群管理类Cluster将多个Invoker合并成一个实例。</p>
<ol>
<li>调用Spring接口解析XML或注解生成一个ReferenceBean，该Bean实现了InitializingBean接口，所以在该Bean被Spring容器实例化后会自动调用afterPropertiesSet()方法。</li>
<li>调用本类的getObject() –&gt; 父类ReferenceConfig的get()方法 –&gt; init() –&gt; createProxy(Map&lt;String, String&gt;)方法。</li>
<li>createProxy(Map&lt;String, String&gt;)方法会首先根据配置检查是否为本地调用，若是，则调用InjvmProtocol的refer方法生成InjvmInvoker实例。然后判断是否需要向注册中心请求服务提供方的地址，如果dubbo:reference标签中没有配置url属性就会调用loadRegistries(boolean)方法去加载注册中心。</li>
<li>如果是dubbo协议，会调用DubboProtocol的refer(Class<t>, URL) –&gt; getClients(URL) –&gt; getSharedClient(URL) –&gt; initClient(URL) –&gt; Exchangers的connect(URL, ExchangeHandler)方法 –&gt; HeaderExchanger的connect(URL, ExchangeHandler)方法 –&gt; Transporters的connect(URL, ChannelHandler…)方法 –&gt; NettyTransporter的connect(URL, ChannelHandler)方法。</t></li>
<li>新建一个NettyClient实例，NettyClient的父类AbstractClient的构造方法会调用NettyClient的doOpen()方法。doOpen方法中实现了Netty客户端连接到服务器端的逻辑。</li>
</ol>
<h3 id="Invoker模型"><a href="#Invoker模型" class="headerlink" title="Invoker模型"></a>Invoker模型</h3><p>Invoker是Dubbo的核心模型，代表一个可执行体。在服务提供方，Invoker用于调用服务提供类。在服务消费方，Invoker用于执行远程调用。Invoker是由Protocol实现类构建而来。<br>下图为一个服务的真正调用过程：<br><img src="/img/Dubbo调用过程.png"></p>
<h2 id="集群容错"><a href="#集群容错" class="headerlink" title="集群容错"></a>集群容错</h2><h3 id="服务目录"><a href="#服务目录" class="headerlink" title="服务目录"></a>服务目录</h3><ul>
<li>存储一些和服务提供者有关的信息，通过服务目录，服务消费者可获得服务提供者的信息，比如ip、端口、服务协议等。通过这些信息，服务消费者就可通过Netty等客户端进行远程调用。</li>
<li>在一个服务集群中，服务提供者数量并不是一成不变的，如果集群中新增了一台机器，相应地在服务目录中就要新增一条服务提供者记录。或者，如果服务提供者的配置修改了，服务目录中的记录也要做相应的更新。</li>
<li>服务目录的服务配置信息是从注册中心获取的。获取到配置信息后会为每条配置信息生成一个Invoker对象，并把这个Invoker对象存储起来，这个Invoker才是服务目录最终持有的对象。Invoker是一个具有远程调用功能的对象。</li>
<li>所以说服务目录可以看做是Invoker集合，且这个集合中的元素会随注册中心的变化而进行动态调整。</li>
</ul>
<h3 id="服务路由"><a href="#服务路由" class="headerlink" title="服务路由"></a>服务路由</h3><p>服务路由包含一条路由规则，路由规则决定了服务消费者的调用目标，即规定了服务消费者可调用哪些服务提供者。</p>
<h3 id="集群"><a href="#集群" class="headerlink" title="集群"></a>集群</h3><p>当出现多个服务提供者时，服务消费者需要决定选择哪个服务提供者进行调用。另外服务调用失败时的处理措施也是需要考虑的，是重试呢，还是抛出异常，亦或是只打印异常等。集群Cluster用途是将多个服务提供者合并为一个Cluster Invoker，并将这个Invoker暴露给服务消费者。这样一来，服务消费者只需通过这个Invoker进行远程调用即可，至于具体调用哪个服务提供者，以及调用失败后如何处理等问题，现在都交给集群模块去处理。</p>
<h3 id="负载均衡"><a href="#负载均衡" class="headerlink" title="负载均衡"></a>负载均衡</h3><p>负载均衡的责任是将网络请求，或者其他形式的负载均摊到不同的机器上。避免集群中部分服务器压力过大，而另一些服务器比较空闲的情况。</p>

      
    </div>
      
      <footer>
        
        
  
  <div class="tags">
    <a href="/tags/分布式/">分布式</a>
  </div>

        
  <div class="addthis addthis_toolbox addthis_default_style">
    
      <a class="addthis_button_facebook_like" fb:like:layout="button_count"></a>
    
    
      <a class="addthis_button_tweet"></a>
    
    
      <a class="addthis_button_google_plusone" g:plusone:size="medium"></a>
    
    
      <a class="addthis_button_pinterest_pinit" pi:pinit:layout="horizontal"></a>
    
    <a class="addthis_counter addthis_pill_style"></a>
  </div>
  <script type="text/javascript" src="//s7.addthis.com/js/300/addthis_widget.js"></script>

      
      <div class="clearfix"></div>
      </footer>
  </div>
</article>

<!-- 来必力City版安装代码 -->
<!-- <div id="lv-container" data-id="city" data-uid="MTAyMC8yOTQ5MS82MDU5">
<script type="text/javascript">
   (function(d, s) {
       var j, e = d.getElementsByTagName(s)[0];

       if (typeof LivereTower === 'function') { return; }

       j = d.createElement(s);
       j.src = 'https://cdn-city.livere.com/js/embed.dist.js';
       j.async = true;

       e.parentNode.insertBefore(j, e);
   })(document, 'script');
</script>
<noscript>为正常使用来必力评论功能请激活JavaScript</noscript>
</div> -->
<!-- City版安装代码已完成 -->

</div></div>
    <aside id="sidebar" class="alignright">
  
<div class="widget tagcloud">
  <h3 class="title">文章分类</h3>
  <div class="entry">
    <a href="/tags/JavaWeb/" style="font-size: 17.14px;">JavaWeb</a> <a href="/tags/Java基础/" style="font-size: 15.71px;">Java基础</a> <a href="/tags/Java高级特性/" style="font-size: 15.71px;">Java高级特性</a> <a href="/tags/Python/" style="font-size: 10px;">Python</a> <a href="/tags/Spring基础/" style="font-size: 14.29px;">Spring基础</a> <a href="/tags/WebRTC/" style="font-size: 10px;">WebRTC</a> <a href="/tags/其它/" style="font-size: 20px;">其它</a> <a href="/tags/分布式/" style="font-size: 11.43px;">分布式</a> <a href="/tags/硬件/" style="font-size: 12.86px;">硬件</a> <a href="/tags/计算机基础/" style="font-size: 18.57px;">计算机基础</a> <a href="/tags/设计模式/" style="font-size: 17.14px;">设计模式</a>
  </div>
</div>



    <div class="widget tagcloud">
    <h3 class="title">电子书</h3>
    <ul class="entry">
      <li class='link'><a href='http://www.duokan.com/' target="_blank">多看阅读</a>&nbsp;&nbsp;<a href='https://read.douban.com/' target="_blank">豆瓣阅读</a>&nbsp;&nbsp;<a href='http://e.dangdang.com/' target="_blank">当当阅读</a></li>
      <li class='link'><a href='http://www.ituring.com.cn/' target="_blank">图灵社区</a>&nbsp;&nbsp;<a href='https://www.epubit.com/' target="_blank">异步社区</a>&nbsp;&nbsp;<a href='https://www.geekbang.org/' target="_blank">极客空间</a></li>
    </ul>
  </div>


  
  <div class="widget tag">
    <h3 class="title">友情链接</h3>
      <ul class="entry">
        
          <li class='link'><a href='http://www.cnblogs.com/jietang/' target="_blank">唐洁的博客</a></li>
        
          <li class='link'><a href='http://rednaxelafx.iteye.com/' target="_blank">R大的博客</a></li>
        
          <li class='link'><a href='http://www.hollischuang.com/' target="_blank">阿里大神的博客</a></li>
        
          <li class='link'><a href='http://blog.csdn.net/IT_faquir/' target="_blank">IT_faquir的专栏</a></li>
        
      </ul>
  </div>


  
  <div class="widget tag">
    <h3 class="title">思维导图</h3>
      <ul class="entry">
        
          <li class='link'><a href='https://fengbo4213.github.io/img/开源协议.png' target="_blank">开源协议简介</a></li>
        
          <li class='link'><a href='https://www.processon.com/diagraming/5b0cf757e4b009aef58d4b9d' target="_blank">我的知识体系</a></li>
        
          <li class='link'><a href='https://www.processon.com/view/link/5a2a00e5e4b015e677290b4f' target="_blank">简单的网站架构</a></li>
        
      </ul>
  </div>


  
  <div class="widget tag">
    <h3 class="title">在线手册</h3>
      <ul class="entry">
        
          <li class='link'><a href='http://v3.bootcss.com/' target="_blank">BootStrap官方手册</a></li>
        
          <li class='link'><a href='http://www.iconfont.cn/http://element.eleme.io/#/zh-CN/' target="_blank">阿里巴巴矢量图标库</a></li>
        
      </ul>
  </div>


  
<div class="widget tag">
  <h3 class="title">最新文章</h3>
  <ul class="entry">
    
      <li>
        <a href="/2123/08/30/基础之MySQL原理/">MySQL原理</a>
      </li>
    
      <li>
        <a href="/2024/01/01/其它之ClickHouse索引/">ClickHouse索引</a>
      </li>
    
      <li>
        <a href="/2023/12/17/其它之MySQL线程池/">MySQL线程池</a>
      </li>
    
      <li>
        <a href="/2023/12/16/其它之InnoDB性能优化基础/">InnoDB性能优化基础</a>
      </li>
    
      <li>
        <a href="/2023/12/03/其它之Java8之后的版本新特性/">Java8之后的版本新特性</a>
      </li>
    
  </ul>
</div>

</aside>
    <div class="clearfix"></div>
  </div>
  <footer id="footer" class="inner"><div class="alignleft">
  
  &copy; 2024 fengbo
  
</div>
<div class="clearfix"></div></footer>
  <script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>



</body>
</html>

